﻿using Core.Framework;
using DotNetty.Buffers;
using DotNetty.Codecs.Http;
using DotNetty.Codecs.Http.Multipart;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Common.Utilities;
using DotNetty.Framework.BaseModel;
using DotNetty.Framework.ExtendChannelOfClient;
using DotNetty.Transport.Channels;
using iTool.ClusterComponent;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace DotNetty.Framework
{
    /// <summary>
    /// 负责建立通信通道
    /// </summary>
    public class AggregatedFullHttpRequestHandler<TMessage, THttpContext> : SimpleChannelInboundHandler<IFullHttpRequest>
        where TMessage : AbstractMessage
        where THttpContext : AbstractHttpContext, new()
    {
        protected WebSocketServerHandshaker Handshaker { get; set; }

        IWebSocketEventTriggered<TMessage> eventTriggered;
        IHttpEventTriggered<THttpContext> httpEventTriggered;
        IBindChannelInfo bindChannelInfo;

        public AggregatedFullHttpRequestHandler(
            IWebSocketEventTriggered<TMessage> eventTriggered,
            IHttpEventTriggered<THttpContext> httpEventTriggered,
            IBindChannelInfo bindChannelInfo)
        {
            this.eventTriggered = eventTriggered;
            this.httpEventTriggered = httpEventTriggered;
            this.bindChannelInfo = bindChannelInfo;
        }

        protected async override void ChannelRead0(IChannelHandlerContext context, IFullHttpRequest request)
        {
            if (Common.IsShutdowned)
            {
                return;
            }

            try
            {
                //SendHttpResponse(context, request, new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.Forbidden));
                if (!request.Result.IsSuccess)
                {
                    await context.CloseAsync();
                    return;
                }

                // WebSocket
                if (this.IsWebSocket(request.Headers))
                {
                    await this.HandleWebSocketRequest(context, request);
                    await this.eventTriggered.OnConnect(context);
                }
                // Http
                else
                {
                    await this.HandleHttpRequest(context, request);
                }

            }
            catch (Exception ex)
            {
                await this.eventTriggered.OnError(context, new OnExceptionContext
                {
                    Action = "AggregatedFullHttpRequestHandler.ChannelRead0",
                    Exception = ex,
                    Message = request
                });
            }
        }

        #region Handel

        private async Task HandleWebSocketRequest(IChannelHandlerContext context, IFullHttpRequest request)
        {
            var parameter = await this.GetWebSocketParameters(context, request);

            var webSocketFactory = new WebSocketServerHandshakerFactory(parameter.location, parameter.protocol, true);

            this.Handshaker = webSocketFactory.NewHandshaker(request);

            if (this.Handshaker == null)
            {
                await WebSocketServerHandshakerFactory.SendUnsupportedVersionResponse(context.Channel);
            }
            else
            {
                await this.Handshaker.HandshakeAsync(context.Channel, request);
            }

            base.HandlerAdded(context);
        }

        private async Task HandleHttpRequest(IChannelHandlerContext context, IFullHttpRequest request)
        {
            // /favicon.ico
            if (request.Uri == "/favicon.ico")
            {
                await context.Channel.CloseAsync();
                return;
            }

            // 构造请求上下文
            var httpContext = new THttpContext();
            httpContext.Host = this.GetHttpRequstHost(request);
            if (request.Uri.IndexOf("?") > -1)
            {
                string[] arr = request.Uri.Split('?');
                httpContext.Path = arr[0].StartsWith("/connector") ? (arr[0].Length == 10 ? "/" : arr[0].Substring(10)) : arr[0];
                httpContext.QueryString = request.Uri.Substring(httpContext.Path.Length + 1);
            }
            else
            {
                httpContext.Path = request.Uri;
            }
            httpContext.Channel = this.GetChannel(request.Headers);
            httpContext.Headers = request.Headers;
            httpContext.Method = request.Method;

            //创建HTTP对象工厂
            IHttpDataFactory factory = new DefaultHttpDataFactory();
            //使用HTTP POST解码器
            HttpPostRequestDecoder httpDecoder = new HttpPostRequestDecoder(factory, request);
            httpDecoder.DiscardThreshold = 0;

            if (httpDecoder.IsMultipart)
            {
                httpContext.Body = new byte[0];
                httpContext.Files = httpContext.Files ?? new List<IFileUpload>();
                httpContext.FormBody = httpContext.FormBody ?? new Dictionary<string, string>();

                var InterfaceHttpDataList = httpDecoder.GetBodyHttpDatas();
                foreach (var data in InterfaceHttpDataList)
                {

                    if (data is IFileUpload file)
                    {
                        httpContext.Files.Add(file);
                    }

                    if (data is MemoryAttribute param)
                    {
                        httpContext.FormBody.Add(param.Name, param.Value);
                    }
                }
            }
            else
            {
                httpContext.Body = this.GetHttpContent(request);
            }


            // 调用处理器
            try
            {
                // 执行前

                await this.httpEventTriggered.BeforeAsync(httpContext);
                // 执行任务
                await this.httpEventTriggered.ActionAsync(httpContext);

                // 执行后
                await this.httpEventTriggered.AfterAsync(httpContext);

                // 构造Response
                var response = new DefaultFullHttpResponse(HttpVersion.Http11, httpContext.ResponseStatus);
                // 写入Header信息
                foreach (var item in httpContext.ResponseHeader)
                    response.Headers.Set(item.Key, item.Value);

                // 写入响应信息
                IByteBuffer buffer;
                if (httpContext.ResponseBody == null)
                {
                    buffer = Unpooled.CopiedBuffer(string.Empty, httpContext.ResponseEncoding);
                }
                else if (httpContext.ResponseBody is string bodyString)
                {
                    buffer = Unpooled.CopiedBuffer(bodyString, httpContext.ResponseEncoding);
                }
                else if (httpContext.ResponseBody is byte[] bodyBytes)
                {
                    buffer = Unpooled.CopiedBuffer(bodyBytes);
                }
                // 如果是文件
                else if (httpContext.ResponseBody is FileStream file)
                {
                    BinaryReader binaryReader = new BinaryReader(file);
                    buffer = Unpooled.CopiedBuffer(binaryReader.ReadBytes((int)file.Length));

                    // set type
                    response.Headers.Add(new AsciiString("Content-Type"), FileContentType.GetMimeType(file.Name));
                    // cache
                    response.Headers.Add(new AsciiString("Pragma"), "public");
                    response.Headers.Add(new AsciiString("Cache-Control"), "public");
                    // set filename
                    response.Headers.Add(new AsciiString("Content-Disposition"), $"filename=\"{file.Name}\"");
                    await file.DisposeAsync();
                }
                // 如果是文件
                else if (httpContext.ResponseBody is FileDetails fileResult)
                {
                    BinaryReader binaryReader = new BinaryReader(fileResult.StreamData);
                    buffer = Unpooled.CopiedBuffer(binaryReader.ReadBytes((int)fileResult.StreamData.Length));

                    // set type
                    response.Headers.Add(new AsciiString("Content-Type"), fileResult.ContentType);
                    // cache
                    response.Headers.Add(new AsciiString("Pragma"), "public");
                    response.Headers.Add(new AsciiString("Cache-Control"), "public");
                    // set filename
                    response.Headers.Add(new AsciiString("Content-Disposition"), $"filename=\"{fileResult.Name}\"");
                    await fileResult.StreamData.DisposeAsync();
                }

                // 如果是流
                else if (httpContext.ResponseBody is Stream stream)
                {
                    BinaryReader binaryReader = new BinaryReader(stream);
                    buffer = Unpooled.CopiedBuffer(binaryReader.ReadBytes((int)stream.Length));
                    await stream.DisposeAsync();
                }
                else
                {
                    buffer = Unpooled.CopiedBuffer(httpContext.ResponseBody?.ToString(), httpContext.ResponseEncoding);
                }

                response.Content.WriteBytes(buffer);

                if (buffer.ReferenceCount > 0)
                {
                    buffer.Release();
                }

                HttpUtil.SetContentLength(response, response.Content.ReadableBytes);

                // 推送消息到客户端
                await context.Channel.WriteAndFlushAsync(response);
                // 推送成功后关闭连接
                await context.Channel.CloseAsync();

            }
            catch (Exception exception)
            {
                // 构造Response
                var response = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.InternalServerError);

                // 写入Header信息
                foreach (var item in httpContext.ResponseHeader)
                    response.Headers.Set(item.Key, item.Value);

                IByteBuffer buffer = exception.InnerException == null ? Unpooled.CopiedBuffer($"exception message:{exception.Message} \n{exception.StackTrace}", httpContext.ResponseEncoding) : Unpooled.CopiedBuffer($"exception message:{exception.InnerException.Message} \n{exception.InnerException.StackTrace}", httpContext.ResponseEncoding);

                response.Content.WriteBytes(buffer);
                HttpUtil.SetContentLength(response, response.Content.ReadableBytes);

                // 推送消息到客户端
                await context.Channel.WriteAndFlushAsync(response);

                // 推送成功后关闭连接
                await context.Channel.CloseAsync();

                // 发生异常
                await this.httpEventTriggered.ErrorAsync(httpContext, exception);
            }
        }


        #endregion

        #region 私有方法

        private bool IsWebSocket(HttpHeaders headers)
        {
            if (headers.TryGetAsString(HttpHeaderNames.Upgrade, out string value))
                return value.ToLower() == "websocket";

            return false;
        }

        private async Task<(string location, string protocol)> GetWebSocketParameters(IChannelHandlerContext context, IFullHttpRequest request)
        {

            string location = this.GetHttpRequstHost(request);
            string protocol = this.GetHttpRequstProtocols(request);
            ConnectorInfo channelClientInfo = default;

            if (string.IsNullOrWhiteSpace(location))
                throw new Exception("host is null");

            try
            {
                if (!string.IsNullOrWhiteSpace(protocol))
                {
                    var protocols = protocol.Split(',');
                    var parameters = AESEncrypt.Decrypt(protocols[1], protocols[0]);
                    channelClientInfo = parameters.TryToEntity<ConnectorInfo>();
                    channelClientInfo.UUID = protocols[0];
                }
            }
            catch
            {

            }
            finally
            {
                channelClientInfo = channelClientInfo ?? new ConnectorInfo();
            }

            channelClientInfo.Host = location;
            channelClientInfo.Ip = this.GetConnectionIp(context, request.Headers);

            // 用户身份
            string token = this.GetHttpRequstToken(request);
            if (!string.IsNullOrWhiteSpace(token))
            {
                try
                {
                    await this.bindChannelInfo.BindIdentityAsync(context, token);
                }
                catch
                {
                    await context.CloseAsync();
                }
            }

            try
            {
                await this.bindChannelInfo.BindClientAsync(context, channelClientInfo);
            }
            catch
            {

            }

            return ("ws://" + location, protocol);
        }

        private string GetHttpRequstHost(IFullHttpRequest request)
        {
            bool isHavHost = request.Headers.TryGetAsString(HttpHeaderNames.Host, out string value);
            return isHavHost ? value : string.Empty;
        }

        private string GetHttpRequstProtocols(IFullHttpRequest request)
        {
            bool isHavProtocol = request.Headers.TryGetAsString(HttpHeaderNames.SecWebsocketProtocol, out string value);
            return isHavProtocol ? value : string.Empty;
        }

        private string GetChannel(HttpHeaders headers)
        {
            string channel = string.Empty;
            // Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0IiwiZXhwIjoxNjM2MDM1NzYyfQ.8uI3OueA_YBs4NT0_OPLz4SSnddCYBqqJjHuJBA4ztM
            if (headers.TryGetAsString(HttpHeaderNames.Authorization, out string token))
            {
                token = token.TrimStart("Bearer ".ToCharArray());
                if (!string.IsNullOrWhiteSpace(token))
                {
                    var jwt = new iToolJwtSecurity(AppSetting.iToolConfigure.JWTToken ?? "help-itool@qq.com");
                    var claims = jwt.GetClaims(token);
                    if (claims != null)
                    {
                        channel = jwt.GetChannel(claims);
                    }
                }
            }

            return channel;
        }

        private string GetHttpRequstToken(IFullHttpRequest request)
        {
            string uri = request.Uri.ToString();
            var uriArr = uri.Split('?');
            if (uriArr.Length > 1)
            {
                LoggerUtils.Write($"GetHttpRequstToken:{uriArr[1]},uri:{uri}");

                return uriArr[1];
            }
            else
            {
                return string.Empty;
            }
        }

        private byte[] GetHttpContent(IFullHttpRequest request)
        {
            byte[] req = new byte[request.Content.ReadableBytes];
            request.Content.ReadBytes(req);
            return req;
        }

        private string GetConnectionIp(IChannelHandlerContext context, HttpHeaders headers)
        {
            try
            {
                if (!headers.TryGetAsString(new AsciiString("x-real-ip"), out string clientIP))
                {
                    if (!headers.TryGetAsString(HttpHeaderNames.XFrameOptions, out clientIP))
                    {
                        var endPoint = context.Channel.RemoteAddress;
                        System.Net.IPAddress ip = ((System.Net.IPEndPoint)endPoint).Address;
                        clientIP = ip.ToString();
                    }
                }

                return clientIP;
            }
            catch (Exception)
            {
                return string.Empty;
            }
        }
        #endregion
    }
}
